HOME/Articles/

pegination (1)

Article Outline

Example Python program pegination (1).py Python version 3.x or newer. To check the Python version use:

python --version

Modules

  • import tkinter as tk
  • import tkinter.ttk as ttk
  • from tkinter import font as tkFont
  • from tkinter.constants import *
  • from Tkinter import Tk
  • from tkMessageBox import showinfo
  • from tkinter import Tk
  • from tkinter.messagebox import showinfo

Classes

  • class Page_Label(tk.Label, object):
  • class Page_Control(tk.Frame):
  • class Left_Control(Page_Control):
  • class Right_Control(Page_Control):
  • class Pagination(tk.Frame):

Methods

  • def config_style(label, style, is_selected, is_active):
  • def init(self, master, page_number, style, on_click, is_selected, is_active, is_displayed=True):
  • def page_number(self):
  • def page_number(self, number):
  • def _on_enter(self, event):
  • def _on_leave(self, event):
  • def change_state(self, is_selected, is_active):
  • def _navigation_control(self, pagination, style, control_name, text):
  • def init(self, pagination, style, first_button, prev_button, spacing):
  • def init(self, pagination, style, last_button, next_button, spacing):
  • def init(self, master, displayed_pages, total_pages, background=None, current_page=None, start_page=1,
  • def _render_pagination(self, current_page, prev_button, next_button, first_button, last_button, displayed_pages, start_page, end_page, spacing, hide_controls_at_edge):
  • def _update_labels(self):
  • def _create_configuration_of_state(self, pagination_style, selected_state, active_state):
  • def select_page(self, page_number, start_page=None):
  • def prev_page(self):
  • def next_page(self):
  • def first_page(self):
  • def last_page(self):
  • def current_page(self):
  • def total_pages(self):
  • def total_pages(self, num):
  • def _on_click_page(self, new_page):
  • def update(self, total_pages, current_page, start_page=1):
  • def print_page(page_number):

Code

Python tkinter example

import tkinter as tk
import tkinter.ttk as ttk
from tkinter import font as tkFont
from tkinter.constants import *


# Page label states
NOT_SELECTED = 0
SELECTED = 1

NORMAL_STATE = 0
ACTIVE_STATE = 1


def config_style(label, style, is_selected, is_active):
    label.config(**style[is_selected, is_active])


class Page_Label(tk.Label, object):
    def __init__(self, master, page_number, style, on_click, is_selected, is_active, is_displayed=True):
        tk.Label.__init__(self, master, width=0, text=page_number)

        if is_selected:
            current_style = style[SELECTED, NORMAL_STATE]
        else:
            current_style = style[NOT_SELECTED, NORMAL_STATE]

        self.config(**current_style)

        self._style = style
        self.bind("<1>", lambda event: on_click(self))

        self.bind("<Enter>", self._on_enter)
        self.bind("<Leave>", self._on_leave)

        self._page_number = page_number

        self.is_selected = is_selected
        self.is_active = is_active

        self.is_displayed = is_displayed

    @property
    def page_number(self):
        return self._page_number

    @page_number.setter
    def page_number(self, number):
        self._page_number = number
        self.config(text=str(number))

    def _on_enter(self, event):
        if self.is_selected:
            self.change_state(SELECTED, ACTIVE_STATE)
        else:
            self.change_state(NOT_SELECTED, ACTIVE_STATE)

    def _on_leave(self, event):
        if self.is_selected:
            self.change_state(SELECTED, NORMAL_STATE)
        else:
            self.change_state(NOT_SELECTED, NORMAL_STATE)

    def change_state(self, is_selected, is_active):
        self.is_selected = is_selected
        self.is_active = is_active

        config_style(self, self._style, is_selected, is_active)

class Page_Control(tk.Frame):

    def _navigation_control(self, pagination, style, control_name, text):
        onclick_control = getattr(pagination, "%s_page"%control_name)

        label = tk.Label(self, text=text, width=0)

        config_style(label, style, NOT_SELECTED, NORMAL_STATE)

        label.bind("<1>", lambda event: onclick_control())

        label.bind("<Enter>", lambda event, label=label: config_style(label, style, NOT_SELECTED, ACTIVE_STATE))
        label.bind("<Leave>", lambda event, label=label: config_style(label, style, NOT_SELECTED, NORMAL_STATE))

        return label


class Left_Control(Page_Control):
    def __init__(self, pagination, style, first_button, prev_button, spacing):
        Page_Control.__init__(self, pagination)

        if first_button is None:
            if prev_button is not None:
                self._previous_label = self._navigation_control(pagination, style, "prev", prev_button)
                self._previous_label.pack(side=LEFT)
        else:
            self._first_label = self._navigation_control(pagination, style, "first", first_button)
            self._first_label.pack(side=LEFT)

            if prev_button is not None:
                self._previous_label = self._navigation_control(pagination, style, "prev", prev_button)
                self._previous_label.pack(side=LEFT, padx=(spacing,0))


class Right_Control(Page_Control):
    def __init__(self, pagination, style, last_button, next_button, spacing):
        Page_Control.__init__(self, pagination)
        if last_button is None:
            if prev_button is not None:
                self._next_label = self._navigation_control(pagination, style, "next", next_button)
                self._next_label.pack(side=RIGHT)
        else:
            self._last_label = self._navigation_control(pagination, style, "last", last_button)
            self._last_label.pack(side=RIGHT)

            if next_button is not None:
                self._next_label = self._navigation_control(pagination, style, "next", next_button)
                self._next_label.pack(side=RIGHT, padx=(0, spacing))


class Pagination(tk.Frame):

    def __init__(self, master, displayed_pages, total_pages, background=None, current_page=None, start_page=1,
                 prev_button="<", next_button=">", first_button="|<", last_button=">|",
                 hide_controls_at_edge=False, command=None, pagination_style=None):

        if pagination_style is None:
            raise Exception("No pagination style defined")

        self._start_page = start_page
        self._end_page = min(total_pages, start_page + displayed_pages - 1)

        if current_page is None:
            current_page = start_page
        else:
            if not self._start_page <= current_page <= self._end_page:
                raise ValueError("Not valid selected page")

        tk.Frame.__init__(self, master, background=background)

        self._command = command

        self._hide_controls_at_edge = hide_controls_at_edge

        self._list_of_page_labels = []

        self._total_pages = total_pages
        self._displayed_pages = displayed_pages

        self._left_controls = None
        self._right_controls = None

        self._current_page = current_page

        self._label_spacing = pagination_style.get("button_spacing", 0)

        self._style_config = style_config = {}

        for selected_state in (NOT_SELECTED, SELECTED):
            for active_state in (NORMAL_STATE, ACTIVE_STATE):
                style_config[selected_state, active_state] = self._create_configuration_of_state(pagination_style, selected_state, active_state)

        self._render_pagination(current_page, prev_button, next_button, first_button, last_button, displayed_pages, self._start_page, self._end_page, self._label_spacing, hide_controls_at_edge)

    def _render_pagination(self, current_page, prev_button, next_button, first_button, last_button, displayed_pages, start_page, end_page, spacing, hide_controls_at_edge):
        if prev_button is not None or first_button is not None:
            self._left_controls = Left_Control(self, self._style_config, first_button, prev_button, spacing)

            if hide_controls_at_edge and start_page == 1:
                self._left_controls.is_displayed = False
            else:
                self._left_controls.pack(side=LEFT, padx=(0, self._label_spacing))
                self._left_controls.is_displayed = True

        self._page_frame = tk.Frame(self)
        self._page_frame.pack(side=LEFT)

        for page_number in range(start_page, start_page + displayed_pages):
            is_selected = page_number == self._current_page

            if page_number <= end_page:
                page_label = Page_Label(self._page_frame, page_number, self._style_config, self._on_click_page, is_selected, False, is_displayed=True)

                if page_number == start_page:
                    page_label.pack(side=LEFT)
                else:
                    page_label.pack(side=LEFT, padx=(self._label_spacing, 0))
            else:
                page_label = Page_Label(self._page_frame, page_number, self._style_config, self._on_click_page, is_selected, False, is_displayed=False)

            self._list_of_page_labels.append(page_label)
        if next_button is not None or last_button is not None:
            self._right_controls = Right_Control(self, self._style_config, last_button, next_button, spacing)

            if hide_controls_at_edge and end_page == total_pages:
                self._right_controls.is_displayed = False
            else:
                self._right_controls.pack(side=LEFT, padx=(self._label_spacing, 0))
                self._right_controls.is_displayed = True

    def _update_labels(self):

        if self._hide_controls_at_edge:
            if self._left_controls is not None:
                if self._start_page == 1:
                    if self._left_controls.is_displayed:
                        self._left_controls.is_displayed = False
                        self._left_controls.pack_forget()
                else:
                    if not self._left_controls.is_displayed:
                        self._left_controls.is_displayed = True
                        self._left_controls.pack(side=LEFT, padx=(0, self._label_spacing), before=self._page_frame)

            if self._right_controls is not None:
                if self._end_page == self._total_pages:
                    if self._right_controls.is_displayed:
                        self._right_controls.is_displayed = False
                        self._right_controls.pack_forget()
                else:
                    if not self._right_controls.is_displayed:
                        self._right_controls.is_displayed = True
                        self._right_controls.pack(side=LEFT, padx=(self._label_spacing, 0), after=self._page_frame)

        for i, page_number in enumerate(range(self._start_page, self._end_page+1)):
            page_label = self._list_of_page_labels[i]
            page_label.page_number = page_number

            if self._current_page == page_number:
                if not page_label.is_selected:
                    page_label.change_state(SELECTED, NORMAL_STATE)
            else:
                if page_label.is_selected:
                    page_label.change_state(NOT_SELECTED, NORMAL_STATE)

            if not page_label.is_displayed:
                page_label.is_displayed = True
                if i == 0:
                    page_label.pack(side=LEFT)
                else:
                    page_label.pack_configure(side=LEFT, padx=(self._label_spacing, 0))

        for i in range(self._end_page-self._start_page+1, self._displayed_pages):
            page_label = self._list_of_page_labels[i]

            if page_label.is_displayed:
                page_label.pack_forget()
                page_label.is_displayed = False

    def _create_configuration_of_state(self, pagination_style, selected_state, active_state):
        config = {}

        if "font" in pagination_style:
            config["font"] = pagination_style["font"]

        if "font_family" in pagination_style:
            font_family = pagination_style["font_family"]
        else:
            font_family = None

        if "font_size" in pagination_style:
            font_size = pagination_style["font_size"]
        else:
            font_size = None

        if "font_weight" in pagination_style:
            font_weight = pagination_style["font_weight"]
        else:
            font_weight = None

        if "button_padx" in pagination_style:
            config["padx"] = pagination_style["button_padx"]

        if "button_pady" in pagination_style:
            config["pady"] = pagination_style["button_pady"]

        if selected_state == SELECTED:
            state_style = pagination_style["selected_button"]
        else:
            state_style = pagination_style["normal_button"]

        if active_state == ACTIVE_STATE:
            if "activebackground" in state_style:
                config["background"] = state_style["activebackground"]
            if "activeforeground" in state_style:
                config["foreground"] = state_style["activeforeground"]
        else:
            if "background" in state_style:
                config["background"] = state_style["background"]
            if "foreground" in state_style:
                config["foreground"] = state_style["foreground"]

        if "padx" in state_style:
            config["padx"] = state_style["padx"]

        if "pady" in state_style:
            config["pady"] = state_style["pady"]

        if "font" in state_style:
            config["font"] = state_style["font"]
        else:
            font_family = state_style.get("font_family", font_family)
            font_size = state_style.get("font_size", font_size)
            font_weight = state_style.get("font_weight", font_weight)

            kw = {}
            if font_family is not None:
                kw["family"] = font_family

            if font_size is not None:
                kw["size"] = font_size

            if font_weight is not None:
                kw["weight"] = font_weight

            config["font"] = tkFont.Font(**kw)

        return config

    def select_page(self, page_number, start_page=None):
        self._current_page = page_number
        if start_page is None:
            if page_number < self._start_page:
                self._start_page = page_number
                self._end_page = self._start_page + self._displayed_pages - 1
        else:
            end_page = start_page + self._displayed_pages - 1

            if not start_page <= page_number <= end_page:
                raise ValueError("Page number not visible")

            self._start_page = start_page
            self._end_page = end_page

        self._update_labels()

    def prev_page(self):
        if self._current_page == 1: return

        if self._current_page == self._start_page:
            self._start_page -= 1
            self._end_page -= 1

        self._current_page -= 1

        if self._command is not None:
            self._command(self._current_page)

        self._update_labels()

    def next_page(self):
        if self._current_page == self._total_pages: return

        if self._current_page == self._end_page:
            self._start_page += 1
            self._end_page += 1

        self._current_page += 1

        if self._command is not None:
            self._command(self._current_page)

        self._update_labels()

    def first_page(self):
        if self._current_page == 1: return

        self._start_page = 1
        self._end_page = min(self._total_pages, self._displayed_pages)

        self._current_page = 1

        if self._command is not None:
            self._command(self._current_page)

        self._update_labels()

    def last_page(self):
        if self._current_page == self._total_pages: return

        self._end_page = self._total_pages
        self._start_page = max(self._end_page- self._displayed_pages + 1, 1)

        self._current_page = self._total_pages

        if self._command is not None:
            self._command(self._current_page)

        self._update_labels()

    @property
    def current_page(self):
        return self._current_page

    page = current_page

    @property
    def total_pages(self):
        return self._total_pages

    @total_pages.setter
    def total_pages(self, num):
        self._total_pages = num

        _end_page = self._end_page
        self._start_page = max(min(self._total_pages - self._displayed_pages+1, self._start_page), 1)
        self._end_page = min(self._start_page + self._displayed_pages -1 , self._total_pages)

        if _end_page == self._end_page:
            return
        elif _end_page < self._end_page:
            for page_number in range(_end_page+1, self._end_page +1):
                i = page_number - self._start_page
                page_label = self._list_of_page_labels[i]
                page_label.page_number = page_number

                if i == 0:
                    page_label.pack(side=LEFT)
                else:
                    page_label.pack_configure(side=LEFT, padx=(self._label_spacing, 0))

                page_label.is_displayed = True
        else:
            for i in range(self._end_page - _end_page):
                page_label = self._list_of_page_labels[i]

                page_label.pack_forget()
                page_label.is_displayed = False

    def _on_click_page(self, new_page):
        if new_page.page_number == self._current_page:
            return

        old_page = self._list_of_page_labels[self._current_page - self._start_page]
        old_page.change_state(NOT_SELECTED, NORMAL_STATE)

        new_page.change_state(SELECTED, ACTIVE_STATE)

        self._current_page = new_page.page_number

        if self._command is not None:
            self._command(self._current_page)

    def update(self, total_pages, current_page, start_page=1):
        end_page = min(total_pages, start_page + self._displayed_pages - 1)

        if not start_page <= current_page <= end_page:
            raise ValueError("Not valid selected page")

        self._start_page = start_page
        self._end_page = end_page

        self._total_pages = total_pages
        self._current_page = current_page

        self._update_labels()

pagination_style1 = {
    "button_spacing": 3,
    "button_padx":12,
    "button_pady": 6,
    "normal_button": {
        "font": ("Verdana", 10),
        "foreground": "#337ab7",
        "activeforeground":"#23527c",
        "background": "white",
        "activebackground": "#eee"
    },
    "selected_button": {
        "font":("Verdana", 10, "bold"),
        "foreground":"#fff",
        "activeforeground":"#fff",
        "background":"#337ab7",
        "activebackground":"#337ab7"
    }
}

pagination_style2 = {
    "button_spacing": 3,
    "button_padx": 12,
    "button_pady": 6,
    "normal_button": {
        "font": ("Verdana", 10),
        "foreground": "black",
        "activeforeground":"black",
        "background": "white",
        "activebackground": "#ccc"
    },
    "selected_button": {
        "font":("Verdana", 10, "bold"),
        "foreground":"white",
        "activeforeground":"#fff",
        "background":"#f44336",
        "activebackground":"#f44336"
    }
}

pagination_style3 = {
    "button_spacing": 3,
    "button_padx":12,
    "button_pady":6,
    "normal_button": {
        "font": ("Verdana", 10),
        "foreground": "#717171",
        "activeforeground":"#717171",
        "background": "#e9e9e9",
        "activebackground": "#fefefe"
    },
    "selected_button": {
        "font":("Verdana", 10, "bold"),
        "foreground":"#f0f0f0",
        "activeforeground":"#f0f0f0",
        "background":"#616161",
        "activebackground":"#616161"
    }
}

pagination_style4 = {
    "button_spacing": 3,
    "button_padx":12,
    "button_pady":6,
    "normal_button": {
        "font": ("Verdana", 10),
        "foreground": "#feffff",
        "activeforeground":"#feffff",
        "background": "#3e4347",
        "activebackground": "#3d4f5d"
    },
    "selected_button": {
        "font":("Verdana", 10, "bold"),
        "foreground":"#feffff",
        "activeforeground":"#feffff",
        "background":"#2f3237",
        "activebackground":"#2f3237"
    }
}

pagination_style5 = {
    "button_spacing": 3,
    "button_padx":12,
    "button_pady":6,
    "normal_button": {
        "font": ("Verdana", 10),
        "foreground": "#2E4057",
        "activeforeground":"#2E4057",
        "background": "white",
        "activebackground": "white"
    },
    "selected_button": {
        "font":("Verdana", 10, "bold"),
        "foreground":"white",
        "activeforeground":"white",
        "background":"#64a281",
        "activebackground":"#64a281"
    }
}

pagination_style6 = {
    "button_spacing": 2,
    "button_padx":9,
    "button_pady":4,
    "normal_button": {
        "font": ("Verdana", 7),
        "foreground": "#717171",
        "activeforeground":"#717171",
        "background": "#e9e9e9",
        "activebackground": "#fefefe"
    },
    "selected_button": {
        "font":("Verdana", 7, "bold"),
        "foreground":"#f0f0f0",
        "activeforeground":"#f0f0f0",
        "background":"#616161",
        "activebackground":"#616161"
    }
}

pagination_style7 = {
    "button_spacing": 2,
    "button_padx": 9,
    "button_pady": 4,
    "normal_button": {
        "font": ("Verdana", 7),
        "foreground": "black",
        "activeforeground":"black",
        "background": "white",
        "activebackground": "#eee"
    },
    "selected_button": {
        "font":("Verdana", 7, "bold"),
        "foreground":"#fff",
        "activeforeground":"#fff",
        "background":"#337ab7",
        "activebackground":"#337ab7"
    }
}

if __name__ == "__main__":

    try:
        from Tkinter import Tk
        from tkMessageBox import showinfo
    except ImportError:
        from tkinter import Tk
        from tkinter.messagebox import showinfo

    root = Tk()

    page = tk.Frame()
    page.pack()

    def print_page(page_number):
        print("page number %s"%page_number)

    row = tk.Frame(page)
    row.pack(padx=10, pady=4, fill=X)

    tk.Label(row, text="Pagination").pack(anchor=W)

    pagination = Pagination(row, 5, 100, command=print_page, pagination_style=pagination_style1)
    pagination.pack(pady=10, anchor=W)

    pagination = Pagination(row, 5, 100, command=print_page, pagination_style=pagination_style2)
    pagination.pack(pady=10, anchor=W)

    pagination = Pagination(row, 5, 50, command=print_page,  pagination_style=pagination_style3)
    pagination.pack(pady=10, anchor=W)

    pagination = Pagination(row, 5, 100, command=print_page, pagination_style=pagination_style4)
    pagination.pack(pady=10, anchor=W)

    pagination = Pagination(row, 5, 100, command=print_page, pagination_style=pagination_style6)
    pagination.pack(pady=10, anchor=W)


    root.mainloop()